/** This class decodes and encodes binary files to/from BASE64 
 *  @author Copyright (c) 1999, Bob Withers - bwit@pobox.com
 *  @author Copyright (c) 2005, chrmac@gmx.de */

package info.chrm.helper;

import java.awt.*;
import java.util.*;
import java.io.*;
import javax.swing.JFileChooser;

public class BASE64Handler {
   private static String lastError = null;
   private static int MAX_FILE_SIZE = 307200; // Max 300kb
   private javax.swing.JFrame parentFrame;
   
   /** Constructor */
   public BASE64Handler(javax.swing.JFrame _parentFrame) {
      this.lastError = null;
      this.parentFrame = _parentFrame;
   }

   /** Gets the last error message */
   public String getLastError() {
      return this.lastError;
   }

   /** Lets User select a File and gets it as BASE64 encoded String 
    *  @return A File-Object with name and data or null */
   public EncodedFileObject getEncodedFile() {
      try {
	   JFileChooser fc = new JFileChooser();
	   //int returnVal = fc.showOpenDialog(this);
	   int returnVal = fc.showOpenDialog(this.parentFrame);

	   if (returnVal == JFileChooser.APPROVE_OPTION) {
	       
	       // Open File
	       java.io.File file = fc.getSelectedFile();
	       // Check for file size!!! Max ~ 300kb
               // Server doesn't support bigger files
	       
	       int fileSize = (int)(new FileInputStream(file).getChannel().size());
	       if(fileSize > MAX_FILE_SIZE) {
		  this.lastError = "File is too big! Max size is 300 kB.";
		  return null;
	       }
		  

	       String tmpFilename = file.getAbsolutePath();	       
	       
	       return new EncodedFileObject(file.getName(), 
					    BASE64Handler.getString(BASE64Handler.encode(getBytesFromFile(file))));
	   } 		    
	      
      } catch (Exception _e) { 
	      this.lastError = "Can't load/encode file: " + _e.getMessage();
	      return null;
      } 
	    
      this.lastError = "Action cancelled";
      return null;
   }

   /** Lets User save a BASE64 encoded String as File with given file name */
   public boolean saveDecodedData(String _data, String _filename) {
      if(_data == null || _data.equals("")) {
	 this.lastError = "No Data found.";
	 return false;
      }

      if(_filename == null)
	 _filename = "unknown";
	 
      try {	  
	   JFileChooser fc = new JFileChooser();
           fc.setDialogType(JFileChooser.SAVE_DIALOG);
	   fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
	   //fc.setFileFilter(filter);
	   fc.setSelectedFile(new File(_filename));

	   int returnVal = fc.showSaveDialog(this.parentFrame);

	   if (returnVal == JFileChooser.APPROVE_OPTION) {
	       
	       // Open File
	       java.io.File file = fc.getSelectedFile();			
	       String tmpFilename = file.getAbsolutePath();      
	       
	       FileOutputStream fos = new FileOutputStream(file); 
	       //java.io.BufferedWriter out = new java.io.BufferedWriter(new java.io.FileWriter(tmpFilename));
	       fos.write(BASE64Handler.decode(BASE64Handler.getBinaryBytes(_data)));
	       fos.close();

	       return true;
	   } 		    
	      
      } catch (Exception _e) { 
	    _e.printStackTrace();
	      this.lastError = "Can't save/decode file: " + _e.getMessage();
	      return false;
      } 
	    
      this.lastError = "Action cancelled";
      return false;
   }


   /** Returns the contents of the file in a byte array. */
   public static byte[] getBytesFromFile(File file) throws IOException {
      InputStream is = new FileInputStream(file);
      
      // Get the size of the file
      long length = file.length();
      
      // You cannot create an array using a long type.
      // It needs to be an int type.
      // Before converting to an int type, check
      // to ensure that file is not larger than Integer.MAX_VALUE.
      if (length > Integer.MAX_VALUE) {
	 // File is too large
	 return null;
      }
      
      // Create the byte array to hold the data
      byte[] bytes = new byte[(int)length];
      
      // Read in the bytes
      int offset = 0;
      
      int numRead = 0;
      while (offset < bytes.length && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
	 offset += numRead;      
      }
      
      // Ensure all the bytes have been read in
      if (offset < bytes.length) {
	 throw new IOException("Could not completely read file " + file.getName());
      }

      // Close the input stream and return bytes
      is.close();
      return bytes;      
   }

   /** Returns a BASE64 encoded String */
   public static String encode(String data) {
        return(getString(encode(getBinaryBytes(data))));
   }
   
   /** Returns a BASE64 encoded byte Array */
   public static byte[] encode(byte[] data) {
        int c;
        int len = data.length;
        StringBuffer ret = new StringBuffer(((len / 3) + 1) * 4);
        for (int i = 0; i < len; ++i)
        {
            c = (data[i] >> 2) & 0x3f;
            ret.append(cvt.charAt(c));
            c = (data[i] << 4) & 0x3f;
            if (++i < len)
                c |= (data[i] >> 4) & 0x0f;

            ret.append(cvt.charAt(c));
            if (i < len)
            {
                c = (data[i] << 2) & 0x3f;
                if (++i < len)
                    c |= (data[i] >> 6) & 0x03;

                ret.append(cvt.charAt(c));
            }
            else
            {
                ++i;
                ret.append((char) fillchar);
            }

            if (i < len)
            {
                c = data[i] & 0x3f;
                ret.append(cvt.charAt(c));
            }
            else
            {
                ret.append((char) fillchar);
            }
        }

        return(getBinaryBytes(ret.toString()));
    }

   /** Decodes a BASES64 encoded String */
   public static String decode(String data) {
      return(getString(decode(getBinaryBytes(data))));
   }

   /** Decodes a BASES64 encoded byte Array */
   public static byte[] decode(byte[] data) {
        int c;
        int c1;
        int len = data.length;
        StringBuffer ret = new StringBuffer((len * 3) / 4);
        for (int i = 0; i < len; ++i)
        {
            c = cvt.indexOf(data[i]);
            ++i;
            c1 = cvt.indexOf(data[i]);
            c = ((c << 2) | ((c1 >> 4) & 0x3));
            ret.append((char) c);
            if (++i < len)
            {
                c = data[i];
                if (fillchar == c)
                    break;

                c = cvt.indexOf((char) c);
                c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf);
                ret.append((char) c1);
            }

            if (++i < len)
            {
                c1 = data[i];
                if (fillchar == c1)
                    break;

                c1 = cvt.indexOf((char) c1);
                c = ((c << 6) & 0xc0) | c1;
                ret.append((char) c);
            }
        }

        return(getBinaryBytes(ret.toString()));
    }

   /** Converts a byte Array to String */
   private static String getString(byte[] arr) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < arr.length; ++i)
            buf.append((char) arr[i]);

        return(buf.toString());
   }

   /** Converts a String to a byte Array */
   private static byte[] getBinaryBytes(String str) {
        byte[] b = new byte[str.length()];
        for (int i = 0; i < b.length; ++i)
            b[i] = (byte) str.charAt(i);

        return(b);
   }

   private static final int fillchar = '=';

                                    // 00000000001111111111222222
                                    // 01234567890123456789012345
   private static final String cvt = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

                                    // 22223333333333444444444455
                                    // 67890123456789012345678901
                                    + "abcdefghijklmnopqrstuvwxyz"

                                    // 555555556666
                                    // 234567890123
                                    + "0123456789+/";

   ///////////////////////////////////////////////////////////////////////////////////////
   public static void main(String[] args) {
      // Only for Debug
      BASE64Handler myHandler = new BASE64Handler(null);
      EncodedFileObject tmpFile = myHandler.getEncodedFile();
      System.out.println("FILE: " + tmpFile.filename);
      myHandler.saveDecodedData(tmpFile.data, tmpFile.filename);
   }

   // Helper Class
   public class EncodedFileObject {
      public EncodedFileObject(String _filename, String _data) {
	 this.filename = _filename;
	 this.data = _data;
      }

      public String filename = null;
      public String data = null;
   }

}
